package com.xpalive.utils;


import static com.xpalive.commons.Constants.DateFormatter.DATE;
import static com.xpalive.commons.Constants.DateFormatter.DATE_TIME;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.xpalive.commons.LogPool;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import org.slf4j.Logger;

public class XJsonUtils {

    private XJsonUtils() {
    }

    private static final Logger log = LogPool.LOGGER_CORE;
    public static final ObjectMapper mapper = new ObjectMapper();
    public static final ObjectMapper mapperIgnoreTimeStamp = new ObjectMapper();
    public static final ObjectMapper mapperIgnoreTimeFormatter = new ObjectMapper();


    static {
        setMapperTimeStamp(mapper, mapperIgnoreTimeStamp);
        setMapperIgnoreTimeFormatter(mapper, mapperIgnoreTimeFormatter);
        setMapperIgnore(mapperIgnoreTimeStamp, mapperIgnoreTimeFormatter);
    }

    private static void setMapperIgnore(ObjectMapper... mappers) {
        for (ObjectMapper objectMapper : mappers) {
            setMapperIgnore(objectMapper);
        }
    }

    private static void setMapperIgnore(ObjectMapper mapper) {
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    private static void setMapperTimeStamp(ObjectMapper... mappers) {
        for (ObjectMapper objectMapper : mappers) {
            setMapperTimeStamp(objectMapper);
        }
    }

    private static void setMapperTimeStamp(ObjectMapper mapper) {
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new JsonSerializer<LocalDateTime>() {
            @Override
            public void serialize(LocalDateTime localDateTime, JsonGenerator gen,
                SerializerProvider serializers) throws IOException {
                gen.writeNumber(localDateTime.toInstant(ZoneOffset.ofHours(8)).toEpochMilli());

            }
        });
        javaTimeModule
            .addDeserializer(LocalDateTime.class, new JsonDeserializer<LocalDateTime>() {
                @Override
                public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext ctxt)
                    throws IOException {
                    Long timestamp = jsonParser.getLongValue();
                    return LocalDateTime.ofEpochSecond(timestamp / 1000, 0, ZoneOffset.ofHours(8));
                }
            });

        javaTimeModule.addSerializer(LocalDate.class, new JsonSerializer<LocalDate>() {
            @Override
            public void serialize(LocalDate localDate, JsonGenerator gen,
                SerializerProvider serializers) throws IOException {
                gen.writeNumber(
                    localDate.atStartOfDay().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
            }
        });
        javaTimeModule.addDeserializer(LocalDate.class, new JsonDeserializer<LocalDate>() {
            @Override
            public LocalDate deserialize(JsonParser jsonParser, DeserializationContext ctxt)
                throws IOException {
                Long timestamp = jsonParser.getLongValue();
                return LocalDateTime.ofEpochSecond(timestamp / 1000, 0, ZoneOffset.ofHours(8))
                    .toLocalDate();
            }
        });
        mapper.registerModule(javaTimeModule);
    }

    private static void setMapperIgnoreTimeFormatter(ObjectMapper... mappers) {
        for (ObjectMapper objectMapper : mappers) {
            setMapperIgnoreTimeFormatter(objectMapper);
        }
    }

    private static void setMapperIgnoreTimeFormatter(ObjectMapper mapper) {
        JavaTimeModule localDateModule = new JavaTimeModule();
        localDateModule.addSerializer(LocalDate.class,
            new LocalDateSerializer(DateTimeFormatter.ofPattern(DATE.get())));
        localDateModule.addDeserializer(LocalDate.class,
            new LocalDateDeserializer(DateTimeFormatter.ofPattern(DATE.get())));

        localDateModule.addSerializer(LocalDateTime.class,
            new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME.get())));
        localDateModule.addDeserializer(LocalDateTime.class,
            new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME.get())));
        mapper.registerModule(localDateModule);
    }

    public static <T> Optional<T> transBody2Bean(String body, TypeReference<T> tClass,
        ExceptionCallBack callBack) {
        try {
            return Optional.ofNullable(mapperIgnoreTimeStamp.readValue(body, tClass));
        } catch (JsonProcessingException e) {
            log.error("{}数据解析错误 {}", e.getMessage(), body);
            Optional.ofNullable(callBack).ifPresent(ExceptionCallBack::callBack);
            return Optional.empty();
        }
    }

    public static <T> Optional<T> transBody2Bean(String body, Class<T> tClass,
        ExceptionCallBack callBack) {
        try {
            return Optional.ofNullable(mapperIgnoreTimeStamp.readValue(body, tClass));
        } catch (JsonProcessingException e) {
            log.error("{}数据解析错误 {}", e.getMessage(), body);
            Optional.ofNullable(callBack).ifPresent(ExceptionCallBack::callBack);
            return Optional.empty();
        }
    }

    public static <T> Optional<T> transBody2Bean(String body, Class<T> tClass) {
        return transBody2Bean(body, tClass, null);
    }

    public static <T> Optional<T> transBody2Bean(String body, TypeReference<T> tClass) {
        return transBody2Bean(body, tClass, null);
    }

    @FunctionalInterface
    public interface ExceptionCallBack {

        void callBack();
    }
}
